#include "Symbol.hpp"
#include <stdint.h>
#include <string>

#define FRACTION_BUFFER 20
#define RATIONAL_EPS 1e-15

#ifndef M_E
#define M_E 2.7182818284590452354
#endif

namespace sympy
{
    using namespace baltam::py;
    static int con_frac_buff[FRACTION_BUFFER];
    // [ a0, a1, a2,..., an ]

    struct Fraction
    {
        int64_t p, q;
    };

    Fraction lf2fraction(double x)
    {
        int i = 1, tmp;
        double err = 1.; // = Pi_1^n ai^2
        Fraction ret;
        con_frac_buff[0] = std::floor(x);
        x -= con_frac_buff[0];
        while (i < FRACTION_BUFFER &&
               err > RATIONAL_EPS)
        {
            x = 1 / x;
            double floor_rx = std::floor(x);
            x -= floor_rx;
            con_frac_buff[i] = floor_rx;
            err /= (floor_rx * floor_rx);
            i++;
        }
        if (i >= 3)
        {
            ret.p = 1, ret.q = con_frac_buff[i - 2];
            for (int j = i - 3; j >= 1; j--)
            {
                ret.p = con_frac_buff[j] * ret.q + ret.p;
                tmp = ret.p, ret.p = ret.q, ret.q = tmp;
            }
            ret.p = con_frac_buff[0] * ret.q + ret.p;
        }
        else if (i == 2)
        { // is int
            ret.q = 1;
            ret.p = con_frac_buff[0];
        }
        return ret;
    }

    PyObject *double_to_sym(double val)
    {
        // check special value
        if (val == Py_MATH_PI)
            return pyobject_get_attr_string(sp, "pi");
        if (val == M_E)
            return pyobject_get_attr_string(sp, "E");
        if (val == std::numeric_limits<double>::quiet_NaN())
            return pyobject_get_attr_string(sp, "nan");
        if (val == std::numeric_limits<double>::infinity())
            return pyobject_get_attr_string(sp, "oo");
        if (val == -std::numeric_limits<double>::infinity())
        {
            PyObject *inf = pyobject_get_attr_string(sp, "oo");
            PyObject *__neg__ = pyobject_get_attr_string(inf, "__neg__");
            PyObject *ninf = pyobject_vectorcall_pack(__neg__, 0, 0, 0);
            py_dec_ref(inf), py_dec_ref(__neg__);
            return ninf;
        }
        Fraction r = lf2fraction(val);
        if (std::abs(val - double(r.p) / double(r.q)) <
            RATIONAL_EPS)
        { // check is rational
            if (r.q == 1)
            { // is int
                PyObject *args[2];
                args[1] = pylong_from_long(r.p);
                PyObject *ip = pyobject_vectorcall_pack(Integer, args + 1, 1, 0);
                py_dec_ref(args[1]);
                return ip;
            }
            else
            { // is rational
                PyObject *p = pylong_from_long(r.p);
                PyObject *q = pylong_from_long(r.q);
                PyObject *args = pytuple_new(2);
                pytuple_set_item(args, 0, p);
                pytuple_set_item(args, 1, q);
                PyObject *f = pyobject_call(Rational, args, 0);
                py_dec_ref(args);
                return f;
            }
        }
        else
        {
            PyObject *args[2];
            args[1] = pyfloat_from_double(val);
            PyObject *sf = pyobject_vectorcall_pack(Float, args + 1, 1, 0);
            py_dec_ref(args[1]);
            return sf;
        }
    }

    PyObject *string_to_sym(const std::string_view &s)
    {
        const char *cstr = s.data();
        PyObject *args[2];
        if (*(s.cend() - 1) == ')')
        { // create syms first and then create Function
            // "f(t)", "f(x, y, z)"
            // create symbols first
            size_t pos = s.find('(');
            if (pos == std::string::npos ||
                pos == 0 || pos == s.size() - 2)
            {
                bxErrMsgTxt("请输入正确的符号函数名称：${funcname}(var1, var2, ...)。");
            }
            args[1] = pyunicode_from_string_and_size(cstr + pos + 1, s.size() - pos - 2);
            PyObject *vars = pyobject_vectorcall_pack(symbols, args + 1, 1 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0);
            py_dec_ref(args[1]);
            if (vars == nullptr)
                return nullptr; // error occurred
            // now vars is a tuple of symbols
            // create Function
            args[1] = pyunicode_from_string_and_size(cstr, pos);
            args[0] = pyobject_vectorcall_pack(
                Function, args + 1, 1 | PY_VECTORCALL_ARGUMENTS_OFFSET, nullptr);
            py_dec_ref(args[1]);
            if (args[0] == nullptr)
            { // func creation failed
                py_dec_ref(vars);
                return nullptr; // error occurred
            }
            if (!PyTuple_Check(vars))
            {
                PyObject *tmp = pytuple_new(1);
                pytuple_set_item(tmp, 0, vars);
                vars = tmp; // ensure vars is a tuple
            }
            PyObject *func = pyobject_call(args[0], vars, nullptr);
            py_dec_ref(args[0]);
            if (func == nullptr)
            { // function call vars failed
                py_dec_ref(vars);
                return nullptr; // error occurred
            }
            size_t tp_size = pytuple_size(vars);
            for (size_t i = 0; i < tp_size; i++)
            {
                PyObject *sym = pytuple_get_item(vars, i);
                PyObject *sym_name = pyobject_get_attr_string(sym, "name");
                const char *sym_name_cstr = pyunicode_as_utf8(sym_name);
                py_inc_ref(sym);
                bxAddVariable(sym_name_cstr, bxCreateCStruct(pyobject_sid, sym), bxOVERWRITE);
                py_inc_ref(sym_name);
            }
            py_dec_ref(args[0]);
            return func; // return Function object
        }
        else
        {
            args[1] = pyunicode_from_string_and_size(cstr, s.size());
            args[0] = pyobject_vectorcall_pack(Symbol, args + 1, 1, 0);
            py_dec_ref(args[1]);
            return args[0];
        }
    }

    void bxCreateSymbol(
        int nlhs, bxArray *plhs[],
        int nrhs, const bxArray *prhs[])
    {
        // usage: sym("x"), sym(pi), sym(1.5) -> sym(1,1)
        // sym("x", [2, 3]) -> sym(2, 3):
        // [[x_1_1, x_1_2, x_1_3], [x_2_1, x_2_2, x_2_3]]
        // sym("x%d", [1 10]) not supported
        std::string_view name;
        if (nrhs < 1)
            bxErrMsgTxt("请输入参数不够。");
        if (nrhs == 1)
        {
            if (bxIsChar(prhs[0]) || bxIsString(prhs[0]))
            { // construct from string, create abstract sym
                name = baltam::py::bxAsStringView(prhs[0]);
                if (name.empty())
                    bxErrMsgTxt("请输入正确的字符串。");
                PyObject *s = string_to_sym(name);
                if (!s)
                {
                    if (PluginErr_Occurred())
                        Raise_PluginErr();
                    else if (pyerr_occurred())
                        Raise_PyErr();
                    else
                        bxErrMsgTxt("创建符号失败。");
                    return;
                }
                plhs[0] = bxCreateCStruct(pyobject_sid, s);
            }
            else if (bxIsRealDouble(prhs[0]))
            { // create from const
                const double *val = bxGetDoublesRO(prhs[0]);
                if (bxGetNumberOfElements(prhs[0]) == 1)
                { // create scalar const
                    PyObject *s = double_to_sym(*val);
                    plhs[0] = bxCreateCStruct(pyobject_sid, s);
                }
                else
                {
                    int m = bxGetM(prhs[0]);
                    int n = bxGetN(prhs[0]);
                    PyObject *args[4];
                    args[3] = pytuple_new(m * n);
                    for (int i = 0; i < m; i++)
                    {
                        for (int j = 0; j < n; j++)
                        {
                            PyObject *ij = double_to_sym(*(val + i * n + j));
                            pytuple_set_item(args[3], i * n + j, ij);
                        }
                    }
                    args[1] = pylong_from_long(m);
                    args[2] = pylong_from_long(n);
                    args[0] = pyobject_vectorcall_pack(Matrix, args + 1, 3, nullptr);
                    py_dec_ref(args[1]), py_dec_ref(args[2]), py_dec_ref(args[3]);
                    if (args[0])
                        plhs[0] = bxCreateCStruct(pyobject_sid, args[0]);
                    else
                        bxErrMsgTxt("创建矩阵失败。");
                }
            }
            else if (bxIsExternID(prhs[0], pyobject_sid))
            {
                PyObject *s = (PyObject *)bxGetCStruct(pyobject_sid, prhs[0]);
                py_inc_ref(s);
                plhs[0] = bxCreateCStruct(pyobject_sid, s);
            }
            else
                bxErrMsgTxt("请输入字符串或者数字来创建符号。");
        }
        else if (nrhs == 2)
        {
            if (!bxIsChar(prhs[0]) && !bxIsString(prhs[0]))
                bxErrMsgTxt("请输入正确的字符串作为变量名。");
            name = baltam::py::bxAsStringView(prhs[0]);
            if (bxGetNumberOfElements(prhs[1]) != 2)
                bxErrMsgTxt("请输入[rows cols]。");
            const double *val = bxGetDoublesRO(prhs[1]);
            int rows = int(val[0]), cols = int(val[1]);
            if (rows < 1 || cols < 1)
                bxErrMsgTxt("行列数必须是大于等于1的整数。");
            std::stringstream ss;
            ss << name << "(1:"
               << rows + 1 << "\\,1:"
               << cols + 1 << ')';
            name = ss.str(); // name in format "x(1:m+1\,1:n+1)"
            PyObject *args[4];
            args[1] = pyunicode_from_string_and_size(name.data(), name.size());
            args[3] = pyobject_vectorcall_pack(symbols, args + 1, 1, 0);
            py_dec_ref(args[1]);
            if (args[3] == nullptr)
                bxErrMsgTxt("sym名字解析失败。换一个命名试试？");
            args[1] = pylong_from_long(rows);
            args[2] = pylong_from_long(cols);
            args[0] = pyobject_vectorcall_pack(Matrix, args + 1, 3, nullptr);
            py_dec_ref(args[1]), py_dec_ref(args[2]), py_dec_ref(args[3]);
            if (args[0])
                plhs[0] = bxCreateCStruct(pyobject_sid, args[0]);
            else
                bxErrMsgTxt("创建矩阵失败。");
        }
        else
            bxErrMsgTxt("输入参数数量不正确。请输入sym(name, [rows, cols]).");
    }

    void bxCreateSymbols(
        int nlhs, bxArray *plhs[],
        int nrhs, const bxArray *prhs[])
    {
        if (nrhs == 1)
        {
            std::string_view name = baltam::py::bxAsStringView(prhs[0]);
            if (name.empty())
                bxErrMsgTxt("请输入正确的字符串");
            PyObject *args[2];
            args[1] = pyunicode_from_string_and_size(name.data(), name.size());
            args[0] = pyobject_vectorcall_pack(symbols, args + 1, 1, 0);
            py_dec_ref(args[1]);
            if (args[0] == nullptr)
                bxErrMsgTxt("创建符号失败。");
            ssize_t size = pytuple_size(args[0]);
            if (size < 0)
            {
                py_dec_ref(args[0]);
                bxErrMsgTxt("创建单个符号请使用 sym 函数。\n");
            }
            if (size != nlhs)
            {
                py_dec_ref(args[0]);
                bxPrintf("输出需要%d个参数，但是返回值只有%d个参数。\n", size, nlhs);
                bxErrMsgTxt("输出参数不正确。");
            }
            for (int i = 0; i < nlhs; i++)
            {
                PyObject *ii = pytuple_get_item(args[0], i);
                py_inc_ref(ii);
                plhs[i] = bxCreateCStruct(pyobject_sid, ii);
            }
            py_dec_ref(args[0]);
        }
        else
            bxErrMsgTxt("目前只能输入字符串以构造多个符号。");
    }

    PyObject *ba_to_spobj(const bxArray *ba) noexcept
    {
        PyObject *s = (PyObject *)bxGetCStruct(pyobject_sid, ba);
        if (s)
        {
            py_inc_ref(s);
            return s;
        }
        if (bxIsDouble(ba))
        {
            const double *val = bxGetDoublesRO(ba);
            return double_to_sym(*val);
        }
        return nullptr;
    }
}